home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / email / _parseaddr.py < prev    next >
Encoding:
Text File  |  2006-05-12  |  15.1 KB  |  480 lines

  1. # Copyright (C) 2002-2006 Python Software Foundation
  2. # Contact: email-sig@python.org
  3.  
  4. """Email address parsing code.
  5.  
  6. Lifted directly from rfc822.py.  This should eventually be rewritten.
  7. """
  8.  
  9. __all__ = [
  10.     'mktime_tz',
  11.     'parsedate',
  12.     'parsedate_tz',
  13.     'quote',
  14.     ]
  15.  
  16. import time
  17.  
  18. SPACE = ' '
  19. EMPTYSTRING = ''
  20. COMMASPACE = ', '
  21.  
  22. # Parse a date field
  23. _monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
  24.                'aug', 'sep', 'oct', 'nov', 'dec',
  25.                'january', 'february', 'march', 'april', 'may', 'june', 'july',
  26.                'august', 'september', 'october', 'november', 'december']
  27.  
  28. _daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
  29.  
  30. # The timezone table does not include the military time zones defined
  31. # in RFC822, other than Z.  According to RFC1123, the description in
  32. # RFC822 gets the signs wrong, so we can't rely on any such time
  33. # zones.  RFC1123 recommends that numeric timezone indicators be used
  34. # instead of timezone names.
  35.  
  36. _timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
  37.               'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
  38.               'EST': -500, 'EDT': -400,  # Eastern
  39.               'CST': -600, 'CDT': -500,  # Central
  40.               'MST': -700, 'MDT': -600,  # Mountain
  41.               'PST': -800, 'PDT': -700   # Pacific
  42.               }
  43.  
  44.  
  45. def parsedate_tz(data):
  46.     """Convert a date string to a time tuple.
  47.  
  48.     Accounts for military timezones.
  49.     """
  50.     data = data.split()
  51.     # The FWS after the comma after the day-of-week is optional, so search and
  52.     # adjust for this.
  53.     if data[0].endswith(',') or data[0].lower() in _daynames:
  54.         # There's a dayname here. Skip it
  55.         del data[0]
  56.     else:
  57.         i = data[0].rfind(',')
  58.         if i >= 0:
  59.             data[0] = data[0][i+1:]
  60.     if len(data) == 3: # RFC 850 date, deprecated
  61.         stuff = data[0].split('-')
  62.         if len(stuff) == 3:
  63.             data = stuff + data[1:]
  64.     if len(data) == 4:
  65.         s = data[3]
  66.         i = s.find('+')
  67.         if i > 0:
  68.             data[3:] = [s[:i], s[i+1:]]
  69.         else:
  70.             data.append('') # Dummy tz
  71.     if len(data) < 5:
  72.         return None
  73.     data = data[:5]
  74.     [dd, mm, yy, tm, tz] = data
  75.     mm = mm.lower()
  76.     if mm not in _monthnames:
  77.         dd, mm = mm, dd.lower()
  78.         if mm not in _monthnames:
  79.             return None
  80.     mm = _monthnames.index(mm) + 1
  81.     if mm > 12:
  82.         mm -= 12
  83.     if dd[-1] == ',':
  84.         dd = dd[:-1]
  85.     i = yy.find(':')
  86.     if i > 0:
  87.         yy, tm = tm, yy
  88.     if yy[-1] == ',':
  89.         yy = yy[:-1]
  90.     if not yy[0].isdigit():
  91.         yy, tz = tz, yy
  92.     if tm[-1] == ',':
  93.         tm = tm[:-1]
  94.     tm = tm.split(':')
  95.     if len(tm) == 2:
  96.         [thh, tmm] = tm
  97.         tss = '0'
  98.     elif len(tm) == 3:
  99.         [thh, tmm, tss] = tm
  100.     else:
  101.         return None
  102.     try:
  103.         yy = int(yy)
  104.         dd = int(dd)
  105.         thh = int(thh)
  106.         tmm = int(tmm)
  107.         tss = int(tss)
  108.     except ValueError:
  109.         return None
  110.     tzoffset = None
  111.     tz = tz.upper()
  112.     if _timezones.has_key(tz):
  113.         tzoffset = _timezones[tz]
  114.     else:
  115.         try:
  116.             tzoffset = int(tz)
  117.         except ValueError:
  118.             pass
  119.     # Convert a timezone offset into seconds ; -0500 -> -18000
  120.     if tzoffset:
  121.         if tzoffset < 0:
  122.             tzsign = -1
  123.             tzoffset = -tzoffset
  124.         else:
  125.             tzsign = 1
  126.         tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
  127.     # Daylight Saving Time flag is set to -1, since DST is unknown.
  128.     return yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset
  129.  
  130.  
  131. def parsedate(data):
  132.     """Convert a time string to a time tuple."""
  133.     t = parsedate_tz(data)
  134.     if isinstance(t, tuple):
  135.         return t[:9]
  136.     else:
  137.         return t
  138.  
  139.  
  140. def mktime_tz(data):
  141.     """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
  142.     if data[9] is None:
  143.         # No zone info, so localtime is better assumption than GMT
  144.         return time.mktime(data[:8] + (-1,))
  145.     else:
  146.         t = time.mktime(data[:8] + (0,))
  147.         return t - data[9] - time.timezone
  148.  
  149.  
  150. def quote(str):
  151.     """Add quotes around a string."""
  152.     return str.replace('\\', '\\\\').replace('"', '\\"')
  153.  
  154.  
  155. class AddrlistClass:
  156.     """Address parser class by Ben Escoto.
  157.  
  158.     To understand what this class does, it helps to have a copy of RFC 2822 in
  159.     front of you.
  160.  
  161.     Note: this class interface is deprecated and may be removed in the future.
  162.     Use rfc822.AddressList instead.
  163.     """
  164.  
  165.     def __init__(self, field):
  166.         """Initialize a new instance.
  167.  
  168.         `field' is an unparsed address header field, containing
  169.         one or more addresses.
  170.         """
  171.         self.specials = '()<>@,:;.\"[]'
  172.         self.pos = 0
  173.         self.LWS = ' \t'
  174.         self.CR = '\r\n'
  175.         self.atomends = self.specials + self.LWS + self.CR
  176.         # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
  177.         # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
  178.         # syntax, so allow dots in phrases.
  179.         self.phraseends = self.atomends.replace('.', '')
  180.         self.field = field
  181.         self.commentlist = []
  182.  
  183.     def gotonext(self):
  184.         """Parse up to the start of the next address."""
  185.         while self.pos < len(self.field):
  186.             if self.field[self.pos] in self.LWS + '\n\r':
  187.                 self.pos += 1
  188.             elif self.field[self.pos] == '(':
  189.                 self.commentlist.append(self.getcomment())
  190.             else:
  191.                 break
  192.  
  193.     def getaddrlist(self):
  194.         """Parse all addresses.
  195.  
  196.         Returns a list containing all of the addresses.
  197.         """
  198.         result = []
  199.         while self.pos < len(self.field):
  200.             ad = self.getaddress()
  201.             if ad:
  202.                 result += ad
  203.             else:
  204.                 result.append(('', ''))
  205.         return result
  206.  
  207.     def getaddress(self):
  208.         """Parse the next address."""
  209.         self.commentlist = []
  210.         self.gotonext()
  211.  
  212.         oldpos = self.pos
  213.         oldcl = self.commentlist
  214.         plist = self.getphraselist()
  215.  
  216.         self.gotonext()
  217.         returnlist = []
  218.  
  219.         if self.pos >= len(self.field):
  220.             # Bad email address technically, no domain.
  221.             if plist:
  222.                 returnlist = [(SPACE.join(self.commentlist), plist[0])]
  223.  
  224.         elif self.field[self.pos] in '.@':
  225.             # email address is just an addrspec
  226.             # this isn't very efficient since we start over
  227.             self.pos = oldpos
  228.             self.commentlist = oldcl
  229.             addrspec = self.getaddrspec()
  230.             returnlist = [(SPACE.join(self.commentlist), addrspec)]
  231.  
  232.         elif self.field[self.pos] == ':':
  233.             # address is a group
  234.             returnlist = []
  235.  
  236.             fieldlen = len(self.field)
  237.             self.pos += 1
  238.             while self.pos < len(self.field):
  239.                 self.gotonext()
  240.                 if self.pos < fieldlen and self.field[self.pos] == ';':
  241.                     self.pos += 1
  242.                     break
  243.                 returnlist = returnlist + self.getaddress()
  244.  
  245.         elif self.field[self.pos] == '<':
  246.             # Address is a phrase then a route addr
  247.             routeaddr = self.getrouteaddr()
  248.  
  249.             if self.commentlist:
  250.                 returnlist = [(SPACE.join(plist) + ' (' +
  251.                                ' '.join(self.commentlist) + ')', routeaddr)]
  252.             else:
  253.                 returnlist = [(SPACE.join(plist), routeaddr)]
  254.  
  255.         else:
  256.             if plist:
  257.                 returnlist = [(SPACE.join(self.commentlist), plist[0])]
  258.             elif self.field[self.pos] in self.specials:
  259.                 self.pos += 1
  260.  
  261.         self.gotonext()
  262.         if self.pos < len(self.field) and self.field[self.pos] == ',':
  263.             self.pos += 1
  264.         return returnlist
  265.  
  266.     def getrouteaddr(self):
  267.         """Parse a route address (Return-path value).
  268.  
  269.         This method just skips all the route stuff and returns the addrspec.
  270.         """
  271.         if self.field[self.pos] != '<':
  272.             return
  273.  
  274.         expectroute = False
  275.         self.pos += 1
  276.         self.gotonext()
  277.         adlist = ''
  278.         while self.pos < len(self.field):
  279.             if expectroute:
  280.                 self.getdomain()
  281.                 expectroute = False
  282.             elif self.field[self.pos] == '>':
  283.                 self.pos += 1
  284.                 break
  285.             elif self.field[self.pos] == '@':
  286.                 self.pos += 1
  287.                 expectroute = True
  288.             elif self.field[self.pos] == ':':
  289.                 self.pos += 1
  290.             else:
  291.                 adlist = self.getaddrspec()
  292.                 self.pos += 1
  293.                 break
  294.             self.gotonext()
  295.  
  296.         return adlist
  297.  
  298.     def getaddrspec(self):
  299.         """Parse an RFC 2822 addr-spec."""
  300.         aslist = []
  301.  
  302.         self.gotonext()
  303.         while self.pos < len(self.field):
  304.             if self.field[self.pos] == '.':
  305.                 aslist.append('.')
  306.                 self.pos += 1
  307.             elif self.field[self.pos] == '"':
  308.                 aslist.append('"%s"' % self.getquote())
  309.             elif self.field[self.pos] in self.atomends:
  310.                 break
  311.             else:
  312.                 aslist.append(self.getatom())
  313.             self.gotonext()
  314.  
  315.         if self.pos >= len(self.field) or self.field[self.pos] != '@':
  316.             return EMPTYSTRING.join(aslist)
  317.  
  318.         aslist.append('@')
  319.         self.pos += 1
  320.         self.gotonext()
  321.         return EMPTYSTRING.join(aslist) + self.getdomain()
  322.  
  323.     def getdomain(self):
  324.         """Get the complete domain name from an address."""
  325.         sdlist = []
  326.         while self.pos < len(self.field):
  327.             if self.field[self.pos] in self.LWS:
  328.                 self.pos += 1
  329.             elif self.field[self.pos] == '(':
  330.                 self.commentlist.append(self.getcomment())
  331.             elif self.field[self.pos] == '[':
  332.                 sdlist.append(self.getdomainliteral())
  333.             elif self.field[self.pos] == '.':
  334.                 self.pos += 1
  335.                 sdlist.append('.')
  336.             elif self.field[self.pos] in self.atomends:
  337.                 break
  338.             else:
  339.                 sdlist.append(self.getatom())
  340.         return EMPTYSTRING.join(sdlist)
  341.  
  342.     def getdelimited(self, beginchar, endchars, allowcomments=True):
  343.         """Parse a header fragment delimited by special characters.
  344.  
  345.         `beginchar' is the start character for the fragment.
  346.         If self is not looking at an instance of `beginchar' then
  347.         getdelimited returns the empty string.
  348.  
  349.         `endchars' is a sequence of allowable end-delimiting characters.
  350.         Parsing stops when one of these is encountered.
  351.  
  352.         If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
  353.         within the parsed fragment.
  354.         """
  355.         if self.field[self.pos] != beginchar:
  356.             return ''
  357.  
  358.         slist = ['']
  359.         quote = False
  360.         self.pos += 1
  361.         while self.pos < len(self.field):
  362.             if quote:
  363.                 slist.append(self.field[self.pos])
  364.                 quote = False
  365.             elif self.field[self.pos] in endchars:
  366.                 self.pos += 1
  367.                 break
  368.             elif allowcomments and self.field[self.pos] == '(':
  369.                 slist.append(self.getcomment())
  370.                 continue        # have already advanced pos from getcomment
  371.             elif self.field[self.pos] == '\\':
  372.                 quote = True
  373.             else:
  374.                 slist.append(self.field[self.pos])
  375.             self.pos += 1
  376.  
  377.         return EMPTYSTRING.join(slist)
  378.  
  379.     def getquote(self):
  380.         """Get a quote-delimited fragment from self's field."""
  381.         return self.getdelimited('"', '"\r', False)
  382.  
  383.     def getcomment(self):
  384.         """Get a parenthesis-delimited fragment from self's field."""
  385.         return self.getdelimited('(', ')\r', True)
  386.  
  387.     def getdomainliteral(self):
  388.         """Parse an RFC 2822 domain-literal."""
  389.         return '[%s]' % self.getdelimited('[', ']\r', False)
  390.  
  391.     def getatom(self, atomends=None):
  392.         """Parse an RFC 2822 atom.
  393.  
  394.         Optional atomends specifies a different set of end token delimiters
  395.         (the default is to use self.atomends).  This is used e.g. in
  396.         getphraselist() since phrase endings must not include the `.' (which
  397.         is legal in phrases)."""
  398.         atomlist = ['']
  399.         if atomends is None:
  400.             atomends = self.atomends
  401.  
  402.         while self.pos < len(self.field):
  403.             if self.field[self.pos] in atomends:
  404.                 break
  405.             else:
  406.                 atomlist.append(self.field[self.pos])
  407.             self.pos += 1
  408.  
  409.         return EMPTYSTRING.join(atomlist)
  410.  
  411.     def getphraselist(self):
  412.         """Parse a sequence of RFC 2822 phrases.
  413.  
  414.         A phrase is a sequence of words, which are in turn either RFC 2822
  415.         atoms or quoted-strings.  Phrases are canonicalized by squeezing all
  416.         runs of continuous whitespace into one space.
  417.         """
  418.         plist = []
  419.  
  420.         while self.pos < len(self.field):
  421.             if self.field[self.pos] in self.LWS:
  422.                 self.pos += 1
  423.             elif self.field[self.pos] == '"':
  424.                 plist.append(self.getquote())
  425.             elif self.field[self.pos] == '(':
  426.                 self.commentlist.append(self.getcomment())
  427.             elif self.field[self.pos] in self.phraseends:
  428.                 break
  429.             else:
  430.                 plist.append(self.getatom(self.phraseends))
  431.  
  432.         return plist
  433.  
  434. class AddressList(AddrlistClass):
  435.     """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
  436.     def __init__(self, field):
  437.         AddrlistClass.__init__(self, field)
  438.         if field:
  439.             self.addresslist = self.getaddrlist()
  440.         else:
  441.             self.addresslist = []
  442.  
  443.     def __len__(self):
  444.         return len(self.addresslist)
  445.  
  446.     def __add__(self, other):
  447.         # Set union
  448.         newaddr = AddressList(None)
  449.         newaddr.addresslist = self.addresslist[:]
  450.         for x in other.addresslist:
  451.             if not x in self.addresslist:
  452.                 newaddr.addresslist.append(x)
  453.         return newaddr
  454.  
  455.     def __iadd__(self, other):
  456.         # Set union, in-place
  457.         for x in other.addresslist:
  458.             if not x in self.addresslist:
  459.                 self.addresslist.append(x)
  460.         return self
  461.  
  462.     def __sub__(self, other):
  463.         # Set difference
  464.         newaddr = AddressList(None)
  465.         for x in self.addresslist:
  466.             if not x in other.addresslist:
  467.                 newaddr.addresslist.append(x)
  468.         return newaddr
  469.  
  470.     def __isub__(self, other):
  471.         # Set difference, in-place
  472.         for x in other.addresslist:
  473.             if x in self.addresslist:
  474.                 self.addresslist.remove(x)
  475.         return self
  476.  
  477.     def __getitem__(self, index):
  478.         # Make indexing, slices, and 'in' work
  479.         return self.addresslist[index]
  480.